/* 
	definitions for spu job manager
	singleton implementation

	- it manages the job queue hierarchy, one node queue per individual SPU
	- to each SPU the initial loader is uploaded and the spu driver with the PPU push and pull addresses
	- each job has an ID
	- each job has an automatically generated Execute - function taking the parameter address as input
			this function is automatically generated and takes care of calling the actual job entry function
	- memory areas must be specified to be mapped to SPU
		- it can be issued in any order, on the first run it will get sorted, will be saved into template to be reused for faster processing each time
		- allocations are tracked as well, so a delete and (re)alloc can be performed on SPU and will get mirrored on PPU too
		- static class variables can be handled too
		- null can be specified as size too, this means allocation has to be performed on PPU first, SPU has to wait til it is issued
*/


#ifndef __JOBMAN_SPU_H
#define __JOBMAN_SPU_H

#define JOBSYSTEM_INVOKER_COUNT (128)

#include <platform.h>
#include <MultiThread.h>

#include <IJobManager.h>
#include "SPUDriver/CodePage/SPUPages.h"
#include "SPUDriver/SPULoaderDump.h"
#include "SPUDriver/SPULoaderDefs.h"
#include "SPUMemAreaMan.h"
#include "PPU.h"
#include "SPUJobBase.h"
#include "SPUDriver/Cache/CacheDefs_spu.h"
#include "SPUDriver/JobStructs.h"

#include <map>


///////////////////////////////////////////////////////////////////////////////
// forward declarations
namespace NSPU			{	namespace NElf	{ struct SElfInfo; } }
namespace NPageBin	{ struct SJobStringHandle; }
namespace JobManager { class CSPUBackEnd; }


///////////////////////////////////////////////////////////////////////////////
namespace JobManager
{	
/////////////////////////////////////////////////////////////////////////////
// Util Functions to get access Thread local data (needs to be unique per worker thread)
namespace detail {
	// function to manipulate the per thread fallback job freelist
	void PushToFallbackJobList( JobManager::SInfoBlock *pInfoBlock );
	JobManager::SInfoBlock* PopFromFallbackJobList();

	// functions to access the per thread worker thread id
	void SetWorkerThreadId( uint32 nWorkerThreadId );
	uint32 GetWorkerThreadId();

} // namespace detail 
	

	int PrintOut( const char *fmt, ... );
	//
	// singleton managing the job queues and/for the SPUs
	class CJobManager: public IJobManager
	{
	public:
		// singleton stuff
		static CJobManager*Instance();

		//destructor
		virtual ~CJobManager()
		{
			delete m_pSPUBackEnd;
			delete m_pThreadBackEnd;
			delete m_pFallBackBackEnd;			
		}
				
		VIRTUAL void Init( JobManager::TSPUFreeFunc FreeFunc, JobManager::TSPUMallocFunc MallocFunc, bool bEnablePrintf, uint32 nSysMaxWorker);

		//polls for a spu job (do not use is a callback has been registered)
		VIRTUAL const bool WaitForJob( JobManager::SJobState& rJobState, const int cTimeOutMS=-1) const;
										
		//adds a job
		VIRTUAL const JobManager::EAddJobRes AddJob( JobManager::CJobDelegator& RESTRICT_REFERENCE crJob, const uint32 cOpMode, const JobManager::TJobHandle cJobHandle );

		//obtain job handle from name
		VIRTUAL const JobManager::TJobHandle GetJobHandle(const char* cpJobName, const uint32 cStrLen, JobManager::Invoker pInvoker);
		VIRTUAL const JobManager::TJobHandle GetJobHandle(const char* cpJobName, JobManager::Invoker pInvoker)
		{
			return GetJobHandle(cpJobName, strlen(cpJobName),pInvoker);
		}
		
		VIRTUAL void* Allocate(uint32 size, uint32 alignment = 8)
		{
			return m_MallocFunc(size, alignment);
		}

		VIRTUAL void Free(void* p)
		{
			m_FreeFunc(p);
		}
				

		VIRTUAL const uint32 GetAllocatedMemory() const
		{
			return 0;
		}										
		
		VIRTUAL JobManager::ISPUBackend* GetSPUBackEnd()
		{
			return m_pSPUBackEnd;
		}
		
		//shuts down spu job manager
		VIRTUAL void ShutDown();

		VIRTUAL bool InvokeAsJob( const char *cpJobName ) const;
		VIRTUAL bool InvokeAsJob(const JobManager::TJobHandle cJobHandle) const;
		VIRTUAL bool InvokeAsSPUJob( const char *cpJobName ) const;
		VIRTUAL bool InvokeAsSPUJob(const JobManager::TJobHandle cJobHandle) const;
		VIRTUAL void SetJobFilter( const char *pFilter ) 
		{ 
			m_pJobFilter = pFilter; 
		}
		
		VIRTUAL void SetJobSystemEnabled( int nEnable )
		{
			m_nJobSystemEnabled = nEnable;
		}

		//copy the job parameter into the jobinfo  structure
		static void CopyJobParameter(const uint32 cJobParamSize, void* pDest,	const void* pSrc);		
		
		uint32 GetWorkerThreadId() const;
		
		VIRTUAL JobManager::SJobProfilingData* GetProfilingData();

		void Update(int nJobSystemProfiler);

		VIRTUAL void SetFrameStartTime( const CTimeValue &rFrameStartTime );
		VIRTUAL void AddStartMarker( const char *pName,unsigned int id);
		VIRTUAL void AddStopMarker( unsigned int id );

		ColorB GetRegionColor( unsigned int id );
		JobManager::Invoker GetJobInvoker( uint32 nIdx ) 
		{ 
			assert(nIdx < m_nJobInvokerIdx); 
			assert(nIdx < JOBSYSTEM_INVOKER_COUNT);
			return m_arrJobInvokers[nIdx]; 
		}
		VIRTUAL uint32 GetNumWorkerThreads() const 
		{ 			
			return m_pSPUBackEnd ? m_pSPUBackEnd->GetNumWorkerThreads() : m_pThreadBackEnd ? m_pThreadBackEnd->GetNumWorkerThreads() : 0; 
		}

		// get a free semaphore from the jobmanager pool
		VIRTUAL JobManager::TSemaphoreHandle AllocateSemaphore( volatile const void *pOwner );

		// return a semaphore to the jobmanager pool
		VIRTUAL void DeallocateSemaphore( JobManager::TSemaphoreHandle nSemaphoreHandle, volatile const void *pOwner );

		// increase the refcounter of a semaphore, but only if it is > 0, else returns false
		VIRTUAL bool AddRefSemaphore( JobManager::TSemaphoreHandle nSemaphoreHandle, volatile const void *pOwner ) ;

		// 'allocate' a semaphore in the jobmanager, and return the index of it
		VIRTUAL SJobFinishedConditionVariable* GetSemaphore( JobManager::TSemaphoreHandle nSemaphoreHandle, volatile const void *pOwner );

		VIRTUAL void DumpJobList();

		void IncreaseRunJobs();
		void IncreaseRunFallbackJobs();

		void AddBlockingFallbackJob( JobManager::SInfoBlock* pInfoBlock, uint32 nWorkerThreadID );
	private:
		friend class JobManager::CSPUBackEnd;

		CryCriticalSection m_JobManagerLock;														// lock to protect non-performance critical parts of the jobmanager
		JobManager::Invoker m_arrJobInvokers[JOBSYSTEM_INVOKER_COUNT]; // support 128 jobs for now
		uint32 m_nJobInvokerIdx;
		JobManager::TSPUFreeFunc m_FreeFunc;									//function to free SPU freed memory with
		JobManager::TSPUMallocFunc m_MallocFunc;							//function to alloc SPU memory with		
		const char *m_pJobFilter;
		int m_nJobSystemEnabled;															// should the job system be used	
		bool	 m_Initialized;																	//true if SPUs have been initialized		
		ISPUBackend *m_pSPUBackEnd;
		IBackend *m_pFallBackBackEnd;
		IBackend *m_pThreadBackEnd;
		IBackend *m_pBlockingBackEnd;
		std::set<JobManager::SJobStringHandle> m_registeredJobs;
		
		enum { nSemaphorePoolSize = 16 } ;
		SJobFinishedConditionVariable		m_JobSemaphorePool[nSemaphorePoolSize];
		uint32													m_nCurrentSemaphoreIndex;

		// per frame counter for jobs run/fallback jobs
		uint32 m_nJobsRunCounter;
		uint32 m_nFallbackJobsRunCounter;

		JobManager::SInfoBlock**									m_pRegularWorkerFallbacks;
		uint32																		m_nRegularWorkerThreads;

#if defined(JOBMANAGER_SUPPORT_PROFILING)
		enum {nCapturedFrames = 4};
		std::vector<SJobProfilingData*> m_ProfilingData[nCapturedFrames]; 
		uint32 m_nProfilingDataCounter[nCapturedFrames];
		std::map<JobManager::SJobStringHandle, ColorB> m_JobColors;
		struct SMarker
		{
			const char *pName;
			ColorB color;
			unsigned int id;
			CTimeValue startTime;
			CTimeValue endTime;
		};
		std::vector<SMarker> m_StartStopMarkerStack[nCapturedFrames];
		std::vector<SMarker> m_StartStopMarker[nCapturedFrames];
		std::map<unsigned int, ColorB> m_RegionColors;
		CTimeValue m_FrameStartTime[nCapturedFrames];
		uint32 nFrameIdx;
		ILINE uint32 GetFillFrameIdx() { return nFrameIdx%nCapturedFrames; }
		ILINE uint32 GetPrevFrameIdx() { return (nFrameIdx-1)%nCapturedFrames;}
		ILINE uint32 GetRenderFrameIdx() { return (nFrameIdx-2)%nCapturedFrames;}
		ILINE uint32 GetPrevRenderFrameIdx() { return (nFrameIdx-3)%nCapturedFrames;}
#endif




					
		void GetBlockingPushPullEA( SSPUDriverParameter *pSPUDriverParameter );

		// singleton stuff
		CJobManager();
		// disable copy and assignment
		CJobManager(const CJobManager&);
		CJobManager& operator= (const CJobManager&);
		
					
	
		ILINE void LockFuncProfilingArea()
		{



		}

		ILINE void UnlockFuncProfilingArea()
		{



		}

		ILINE void ResetFuncProfilingArea()
		{
















		}		

	} _ALIGN(128);
}//JobManager


///////////////////////////////////////////////////////////////////////////////
inline void JobManager::CJobManager::CopyJobParameter(const uint32 cJobParamSize, void* pDestParam,	const void* pSrcParam)
{
	assert(IsAligned(cJobParamSize,16) && "JobParameter Size needs to be a multiple of 16");
	assert(cJobParamSize <= JobManager::SInfoBlock::scAvailParamSize && "JobParameter Size larger than available storage");

	SVEC4_UINT *const __restrict pDst = (SVEC4_UINT *const __restrict)(pDestParam);
	SVEC4_UINT *const __restrict pSrc = (SVEC4_UINT *const __restrict)(pSrcParam);
	
#if defined(PLATFORM_64BIT)
	// unrolled loop, assumes that the scAvailParamSize is 208 bytes with 32 bit pointer
	STATIC_CHECK( JobManager::SInfoBlock::scAvailParamSize == 424, ERROR_AVAIBLE_PARAM_SIZE_NOT_EQUALS_424_LOOP_UNROLLING_MUST_BE_ADJUSTED );
#else
	// unrolled loop, assumes that the scAvailParamSize is 208 bytes with 32 bit pointer
	STATIC_CHECK( JobManager::SInfoBlock::scAvailParamSize == 208, ERROR_AVAIBLE_PARAM_SIZE_NOT_EQUALS_208_LOOP_UNROLLING_MUST_BE_ADJUSTED );
#endif

	switch(cJobParamSize)
	{
	case 0: break; // do nothing if we don't have any paramters
	default:
		switch(cJobParamSize/16)
		{		
#if defined(PLATFORM_64BIT)
		case 26:
		case 25:	pDst[25] = pSrc[25];
		case 24:	pDst[24] = pSrc[24];
		case 23:	pDst[23] = pSrc[23];
		case 22:	pDst[22] = pSrc[22];		
		case 21:	pDst[21] = pSrc[21];
		case 20:	pDst[20] = pSrc[20];
		case 19:	pDst[19] = pSrc[19];
		case 18:	pDst[18] = pSrc[18];
		case 17:	pDst[17] = pSrc[17];
		case 16:	pDst[16] = pSrc[16];
		case 15:	pDst[15] = pSrc[15];
		case 14:	pDst[14] = pSrc[14];
		case 13:	pDst[13] = pSrc[13];
#else
		case 13: 
#endif
		case 12:	pDst[12] = pSrc[12];
		case 11:	pDst[11] = pSrc[11];
		case 10:	pDst[10] = pSrc[10];
		case  9:	pDst[ 9] = pSrc[ 9];
		case  8:	pDst[ 8] = pSrc[ 8];
		case  7:	pDst[ 7] = pSrc[ 7];
		case  6:	pDst[ 6] = pSrc[ 6];
		case  5:	pDst[ 5] = pSrc[ 5];
		case  4:	pDst[ 4] = pSrc[ 4];
		case  3:	pDst[ 3] = pSrc[ 3];
		case  2:	pDst[ 2] = pSrc[ 2];
		case  1 :	pDst[ 1] = pSrc[ 1];
		case  0:	pDst[ 0] = pSrc[ 0];		
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
inline void JobManager::CJobManager::IncreaseRunJobs()
{
	CryInterlockedIncrement((int volatile*)&m_nJobsRunCounter);
}

///////////////////////////////////////////////////////////////////////////////
inline void JobManager::CJobManager::IncreaseRunFallbackJobs()
{
	CryInterlockedIncrement((int volatile*)&m_nFallbackJobsRunCounter);
}

#endif //__JOBMAN_SPU_H
